package com.redislock.aop.tool.aop;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;

import com.redislock.aop.tool.annonation.ParamsificationAnnoation;
import com.redislock.aop.tool.annonation.RedisLockAnnotation;
import com.redislock.aop.tool.annonation.TimingIdentificationAnnotation;
import com.redislock.aop.tool.bean.RedisLockDefinitionHolder;
import com.redislock.aop.tool.enums.RedisLockTypeEnum;
import com.redislock.aop.tool.exception.BusinessException;
import com.redislock.aop.tool.exception.SystemException;
import com.redislock.aop.tool.util.ToolUtils;
import com.redislock.aop.tool.util.RedisUtil;
import com.redislock.aop.tool.util.ScheduleUtils;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;

@Aspect
@Component
@Log4j2
public class RedisLockAop {
    @Pointcut("@annotation(com.redislock.aop.tool.annonation.RedisLockAnnotation)")
    public void redisLock() {
    }

    @Around("redisLock()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        RedisLockAnnotation redisLockAnnotation = method.getAnnotation(RedisLockAnnotation.class);
        RedisLockTypeEnum redisLockTypeEnum = redisLockAnnotation.typeEnum();
        //其中TIMINGIDENTIFICATION表示为定时生成的，其他就是采用分布式锁实现幂等
        if(redisLockTypeEnum==RedisLockTypeEnum.TIMINGIDENTIFICATION){
            if(method.isAnnotationPresent(TimingIdentificationAnnotation.class)){
                TimingIdentificationAnnotation timingIdentificationAnnotation =  method.getAnnotation(TimingIdentificationAnnotation.class);
                int flag=timingIdentificationAnnotation.flag();
                String key =   ToolUtils.getTimeKey(flag);
                //若里面有值的话，那么就会报重复
                if(RedisUtil.setnx(key,"1",redisLockAnnotation.lockTime(),redisLockAnnotation.lockTimeUnit())){
                    joinPoint.proceed();
                }else {
                    log.error("错误，{}的定时已经执行了",flag);
                }
            }
            return null;
        }else {
            if(!method.isAnnotationPresent(ParamsificationAnnoation.class)){
               throw new SystemException("不好意思，需要加上参数专用的参数类型识别以及重试次数");
            }
            ParamsificationAnnoation paramsificationAnnoation= method.getAnnotation(ParamsificationAnnoation.class);
            Object[] args = joinPoint.getArgs();
            //获取下办法的第一个参数，把它充当作为key
            String result = StrUtil.str(args[paramsificationAnnoation.lockField()], StandardCharsets.UTF_8);
            String key = redisLockTypeEnum.getUniqueKey(result);
            String value = UUID.fastUUID().toString();
            Object processResult = null;
            if (!RedisUtil.setnx(key, value, redisLockAnnotation.lockTime(), redisLockAnnotation.lockTimeUnit())) {
                throw new BusinessException("不好意思，已经加锁了，请等待谢谢！");
            }
            try {
                Thread thread = Thread.currentThread();
                Long time = System.currentTimeMillis();
                ScheduleUtils.redisLockDefinitionHolders.add(
                        new RedisLockDefinitionHolder(key, redisLockAnnotation.lockTime(), time, thread, paramsificationAnnoation.tryCount())
                );
                processResult = joinPoint.proceed();
                while (thread.isInterrupted()) {
                    throw new BusinessException("线程已经中断了！");
                }
            } catch (Exception e) {
                log.info(e.getMessage());
            } catch (Throwable e) {
                e.printStackTrace();
            } finally {
                RedisUtil.script(key, value);
                log.info("办法执行完毕！");
            }
            return processResult;
        }
    }
}
